#ifndef DATAFLOW_DETAIL_NODEINPUTINTERFACE_H
#define DATAFLOW_DETAIL_NODEINPUTINTERFACE_H

#include <assert.h>

#include <vector>
#include <map>

#include <QMutex>
#include <QMutexLocker>
#include <QWaitCondition>

#include "ArgumentArray.h"
#include "IntVector.h"

namespace DataFlow
{

namespace Detail
{

class NodeInputInterface
{
	public:

		typedef void               BaseType;
		typedef NodeInputInterface ThisType;

		NodeInputInterface(const IntVector & portsCapacities)
			: pcount(int(portsCapacities.size()))
			, ports(portsCapacities.size())
		{
			for (size_t i=0; i<portsCapacities.size(); ++i)
			{
				Port & p = this->ports[i];
				p.setId(int(i));
				p.setCapacity(portsCapacities[i]);
			}
		}

		virtual ~NodeInputInterface(void)
		{
			;
		}

		int portsCount(void) const
		{
			return this->pcount;
		}

		void setPortCapacity(int port, int capacity)
		{
			assert((port >= 0) && (port < this->portsCount()));
			QMutexLocker locker(&(this->mutex));
			this->ports[port].setCapacity(capacity);
		}

		int portCapacity(int port) const
		{
			ThisType * that = const_cast<ThisType *>(this);
			assert((port >= 0) && (port < that->portsCount()));
			QMutexLocker locker(&(that->mutex));
			return that->ports[port].capacity();
		}

		int portSize(int port) const
		{
			ThisType * that = const_cast<ThisType *>(this);
			assert((port >= 0) && (port < that->portsCount()));
			QMutexLocker locker(&(that->mutex));
			return that->ports[port].size();
		}

		int portFreeSize(int port) const
		{
			ThisType * that = const_cast<ThisType *>(this);
			assert((port >= 0) && (port < that->portsCount()));
			QMutexLocker locker(&(that->mutex));
			return that->ports[port].freeSize();
		}

		int portOverflowSize(int port) const
		{
			ThisType * that = const_cast<ThisType *>(this);
			assert((port >= 0) && (port < that->portsCount()));
			QMutexLocker locker(&(that->mutex));
			return that->ports[port].overflowSize();
		}

		int partialSize(void) const
		{
			ThisType * that = const_cast<ThisType *>(this);
			QMutexLocker locker(&(that->mutex));
			return int(that->booked.size());
		}

		int readySize(void) const
		{
			ThisType * that = const_cast<ThisType *>(this);
			QMutexLocker locker(&(that->mutex));
			return int(that->ready.size());
		}

		int isEmpty(void) const
		{
			ThisType * that = const_cast<ThisType *>(this);
			QMutexLocker locker(&(that->mutex));
			return (that->booked.empty() && that->ready.empty() && that->active.empty());
		}

		void insert(int port, int id, const Argument & arg)
		{
			assert((port >= 0) && (port < this->portsCount()));

			this->mutex.lock();
			this->waitPort(port);

			ItemPtrMapIterator it = this->bookItem(id);
			Item * item = it->second;
			item->set(port, arg);

			const bool itemReady = item->isReady();
			if (itemReady)
			{
				this->booked.erase(it);
				this->ready.insert(std::make_pair(id, item));
			}
			this->mutex.unlock();
			//if (itemReady) { this->condNotEmpty.wakeOne(); }
		}

		void insert(const ArgumentArray & args)
		{
			assert(args.size() == this->portsCount());

			this->mutex.lock();
			this->waitPorts();

			const int id = args.id();

			ItemPtrMapIterator it = this->bookItem(id);
			Item * item = it->second;
			item->set(args);

			const bool itemReady = item->isReady();
			if (itemReady)
			{
				this->booked.erase(it);
				this->ready.insert(std::make_pair(id, item));
			}
			this->mutex.unlock();
			//if (itemReady) { this->condNotEmpty.wakeOne(); }
		}

		bool book(int id)
		{
			QMutexLocker locker(&(this->mutex));
			if (this->somePortIsFull()) return false;
			(void)this->bookItem(id);
			return true;
		}

		bool set(int port, int id, const Argument & arg)
		{
			bool itemReady = false;
			{
				QMutexLocker locker(&(this->mutex));
				ItemPtrMapIterator it = this->booked.find(id);
				assert(it != this->booked.end());
				if (it == this->booked.end()) return false;

				Item * item = it->second;
				item->set(port, arg);

				itemReady = item->isReady();
				if (itemReady)
				{
					this->booked.erase(it);
					this->ready.insert(std::make_pair(id, item));
				}
			}

			//if (itemReady) { this->condNotEmpty.wakeOne(); }
			return true;
		}

		/*
		bool getId(int & id)
		{
			id = 0;
			QMutexLocker locker(&(this->mutex));
			if (this->ready.empty()) return false;
			id = this->ready.begin()->first;
			return true;
		}

		bool getIds(IntVector & ids)
		{
			QMutexLocker locker(&(this->mutex));
			if (this->ready.empty()) return false;
			for (ItemPtrMapConstIterator it=this->ready.begin(); it!=this->ready.end(); ++it)
			{
				ids.push_back(it->first);
			}
			return true;
		}

		bool extractInputById(ArgumentArray & args)
		{
			assert(args.size() == this->portsCount());
			const int id = args.id();
			{
				QMutexLocker locker(&(this->mutex));
				ItemPtrMapIterator it = this->ready.find(id);
				assert(it != this->ready.end());
				if (it == this->ready.end()) return false;

				Item * item = it->second;
				assert(item->isReady());
				this->ready.erase(it);

				args = item->arguments();
				delete item;
			}
			this->condNotFull.wakeAll();
			return true;
		}

		bool extractInput(ArgumentArray & args)
		{
			assert(args.size() == this->portsCount());
			{
				QMutexLocker locker(&(this->mutex));
				assert(!this->ready.empty());
				if (this->ready.empty()) return false;

				ItemPtrMapIterator it = this->ready.begin();

				Item * item = it->second;
				assert(item->isReady());
				this->ready.erase(it);

				args = item->arguments();
				delete item;
			}
			this->condNotFull.wakeAll();
		}
		*/

		bool getId(int & id)
		{
			id = 0;
			QMutexLocker locker(&(this->mutex));
			if (this->ready.empty()) return false;

			ItemPtrMapIterator it = this->ready.begin();
			const int nid = it->first;
			Item * item = it->second;
			this->ready.erase(it);

			this->active.insert(std::make_pair(nid, item));
			id = nid;

			return true;
		}

		bool ungetId(int id)
		{
			id = 0;
			QMutexLocker locker(&(this->mutex));
			assert(!this->active.empty());
			if (this->active.empty()) return false;

			ItemPtrMapIterator it = this->active.find(id);
			assert(it != this->active.end());
			if (it == this->active.end()) return false;

			Item * item = it->second;
			this->active.erase(it);
			this->ready.insert(std::make_pair(id, item));

			return true;
		}

		bool extractInputById(ArgumentArray & args)
		{
			assert(args.size() == this->portsCount());
			const int id = args.id();
			{
				QMutexLocker locker(&(this->mutex));
				ItemPtrMapIterator it = this->active.find(id);
				assert(it != this->active.end());
				if (it == this->active.end()) return false;

				Item * item = it->second;
				assert(item->isReady());
				this->active.erase(it);

				for (size_t i=0; i<this->ports.size(); ++i)
				{
					this->ports[i].remove();
				}

				args = item->arguments();
				delete item;
			}
			this->condNotFull.wakeAll();
			return true;
		}

	protected:

		class Item
		{
			public:

				typedef void BaseType;
				typedef Item ThisType;

				Item (void)             : iid(0)                                                            { ; }
				Item (int id, int size) : iid(id), args(id, size), valids(size_t(size)), left(size_t(size)) { ; }

				int  id    (void)   const { return this->iid; }
				void setId (int id)       { this->iid = id;   }

				const ArgumentArray & arguments (void) const { return this->args; }
				ArgumentArray &       arguments (void)       { return this->args; }

				bool isReady (void) const { return (this->left == 0); }

				void set (int index, const Argument & arg)
				{
					assert((index >= 0) && (index < this->args.size()));
					if (!this->valids[index]) { assert(this->left > 0); this->left--; }
					this->args[index] = arg;
				}

				void set (const ArgumentArray & args)
				{
					assert(args.size() == this->args.size());
					this->args = args;
					for (int i=0; i<args.size(); ++i) { this->valids[i] = true; }
					this->left = 0;
				}

			protected:

				typedef std::vector<bool> BoolVector;

				int           iid;
				ArgumentArray args;
				BoolVector    valids;
				size_t        left;
		};

		typedef std::map<int, Item *>      ItemPtrMap;
		typedef ItemPtrMap::iterator       ItemPtrMapIterator;
		typedef ItemPtrMap::const_iterator ItemPtrMapConstIterator;

		class Port
		{
			public:

				typedef void BaseType;
				typedef Port ThisType;

				Port (void)                 : i(0),  c(0),                                 s(0) { ; }
				Port (int id, int capacity) : i(id), c((capacity < 0) ? (0) : (capacity)), s(0) { ; }

				int  id    (void)   const { return this->i; }
				void setId (int id)       { this->i = id;   }

				int  capacity    (void)         const { return this->c;                                }
				void setCapacity (int capacity)       { this->c = ((capacity < 0) ? (0) : (capacity)); }

				int size         (void) const { return this->s;                                          }
				int freeSize     (void) const { if (this->c <= 0) return -1; return ((this->c > this->s) ? (this->c - this->s) : (0)); }
				int overflowSize (void) const { if (this->c <= 0) return  0; return ((this->c < this->s) ? (this->s - this->c) : (0)); }

				bool isEmpty (void) const { return (this->s <= 0);                          }
				bool isFull  (void) const { return ((this->c > 0) && (this->s >= this->c)); }

				void add    (void) { assert((this->c <= 0) || (this->s < this->c)); this->s++; }
				void remove (void) { assert(this->s > 0);                           this->s--; }

			protected:

				int i;
				int c;
				int s;
		};

		typedef std::vector<Port> PortVector;

		QMutex         mutex;
		QWaitCondition condNotFull;
		//QWaitCondition condNotEmpty;

		int            pcount;
		PortVector     ports;
		ItemPtrMap     booked;
		ItemPtrMap     ready;
		ItemPtrMap     active;

		ItemPtrMapIterator bookItem(int id)
		{
			std::pair<ItemPtrMapIterator, bool> pt = this->booked.insert(std::make_pair(id, (Item *)0));
			if (pt.second)
			{
				pt.first->second = new Item(id, this->portsCount());
				for (size_t i=0; i<this->ports.size(); ++i) { this->ports[i].add(); }
			}
			return pt.first;
		}

		bool somePortIsFull(void)
		{
			for (size_t i=0; i<this->ports.size(); ++i) { if (this->ports[i].isFull()) return true; }
			return false;
		}

		void waitPort(int port)
		{
			assert((port >= 0) && (port < this->portsCount()));
			Port & p = this->ports[port];
			while (p.isFull()) { this->condNotFull.wait(&(this->mutex)); }
		}

		void waitPorts(void)
		{
			while (this->somePortIsFull()) { this->condNotFull.wait(&(this->mutex)); }
		}
};

}

}

#endif // DATAFLOW_DETAIL_NODEINPUTINTERFACE_H
